#include "kernel.h"
#include <signal.h>
#include <minix/callnr.h>
#include <minix/com.h>
#include <minix/keymap.h>
#include <ibm/int86.h>
#include "proc.h"
#include "vesa.h"

#include <unistd.h>

PRIVATE unsigned last_wnd_pos = 1;   /* Last position of the video window*/

/* Global variables used by the vesa driver and assembly support. */
PUBLIC u16_t vesa_seg;
PUBLIC vir_bytes vesa_off;	/* video ram is found at vesa_seg:vesa_off */
PUBLIC unsigned vesa_size;	/* 0x2000 for color or 0x0800 for mono */
PUBLIC unsigned vesa_mask;	/* 0x1FFF for color or 0x07FF for mono */

#define WINDOW_SIZE (64*1024)
#define SCREEN_BASE (0xA0000)

FORWARD _PROTOTYPE(void do_open, (message * m_ptr) );
FORWARD _PROTOTYPE(void do_close, (message * m_ptr) );
FORWARD _PROTOTYPE(void do_write, (message *m_ptr) );
FORWARD _PROTOTYPE(void do_read, (message *m_ptr) );
FORWARD _PROTOTYPE(void do_ioctl, (message *m_ptr) );
FORWARD _PROTOTYPE( void set_window, (unsigned window, unsigned position)	);
FORWARD _PROTOTYPE( void vesa_reply, (int code, int replyee, int proc_nr, int status) );
FORWARD _PROTOTYPE( void mode_cap, (void) );
FORWARD _PROTOTYPE(void switch_graph,   ( void ) );
FORWARD _PROTOTYPE(void switch_console, ( void ) );


typedef struct modeinfo_s
{
	u8_t ModeAttributes[2];
	u8_t  WinAAttributes;
	u8_t  WinBAttributes;
	u8_t WinGranularity[2];
	u8_t WinSize[2];
	u8_t WinASegment[2];
	u8_t WinBSegment[2];
	u8_t WinFuncPtr[4];
	u8_t BytesPerScanLine[2];
	u8_t XResolution[2];
	u8_t YResolution[2];
	u8_t  XCharSize;
	u8_t  YCharSize;
	u8_t  NumberOfPlanes;
	u8_t  BitsPerPixel;
	u8_t  NumberOfBanks;
	u8_t  MemoryModel;
	u8_t  BankSize;
	u8_t  NumberOfImagePages;
	u8_t  Reserved_page;
	u8_t  RedMaskSize;
	u8_t  RedMaskPos;
	u8_t  GreenMaskSize;
	u8_t  GreenMaskPos;
	u8_t  BlueMaskSize;
	u8_t  BlueMaskPos;
	u8_t  ReservedMaskSize;
	u8_t  ReservedMaskPos;
	u8_t  DirectColorModeInfo;

	/* VBE 2.0 extensions */
	u8_t PhysBasePtr[4];
	u8_t OffScreenMemOffset[4];
	u8_t OffScreenMemSize[2];

	/* VBE 3.0 extensions */
	u8_t LinBytesPerScanLine[2];
	u8_t  BnkNumberOfPages;
	u8_t  LinNumberOfPages;
	u8_t  LinRedMaskSize;
	u8_t  LinRedFieldPos;
	u8_t  LinGreenMaskSize;
	u8_t  LinGreenFieldPos;
	u8_t  LinBlueMaskSize;
	u8_t  LinBlueFieldPos;
	u8_t  LinRsvdMaskSize;
	u8_t  LinRsvdFieldPos;
	u8_t MaxPixelClock[4];

	u8_t  Reserved[190];
} modeinfo_t;
modeinfo_t modeinfo;

typedef struct {
	int w;
	int h;
	int def;
}resT;

resT resolutions[]={{320,200,0x10F},{640,480,0x112},{800,600,0x115},{1024,768,0x118},{1280,1024,0x11B}};

int res=0;

/*===========================================================================*
 *				mode_cap                                                     *
 *===========================================================================*/

PRIVATE void mode_cap()
{
	/* Set the registers to enable SuperGA Mode */ 
	reg86.b.intno = 0x10;

	/* Choose 'Return VBE Mode Information' */    
	reg86.b.ah = 0x4F;
	reg86.b.al = 0x01;

	reg86.w.cx = resolutions[res].def;

	 /* Get the physical address of modeinfo_t and set it as a parameter */  
	phys2seg(&reg86.w.es, &reg86.w.di, vir2phys(&modeinfo));

	level0(int86);

	printf("\tMode 800x600 32bit\n");
	printf("\tWinSize: %d\n", *((u16_t*)&modeinfo.WinSize));
	printf("\tWinGranularity: %d\n", *((u16_t*)&modeinfo.WinGranularity));
}




/*===========================================================================*
 *				vesa_task                                                    *
 *===========================================================================*/
PUBLIC void vesa_task()
{
/* Main routine of the vesa task. */

  message vesa_mess;		/* buffer for all incoming messages */
  unsigned line;

  /*Set the vesa video memory*/
  phys2seg(&vesa_seg, &vesa_off, SCREEN_BASE);
  vesa_size = 0x10000;
  vesa_size >>= 1;		/* word count */
  vesa_mask = vesa_size - 1;

  /*mode_cap();*/
  /* Display the Arkan Vesa startup banner. */
  printf("Arkan Vesa driver enabled.\n\n");

  while (TRUE)
  {

	receive(ANY, &vesa_mess);

	/* A hardware interrupt is an invitation to check for events. */
	if (vesa_mess.m_type == HARD_INT)
		continue;

	/* Execute the requested function. */
	switch (vesa_mess.m_type)
	{
		case DEV_OPEN:
			do_open(&vesa_mess);
			break;
		case DEV_CLOSE:
			do_close(&vesa_mess);
			break;
		case DEV_READ:
	    		do_read(&vesa_mess);
		    	break;
		case DEV_WRITE:
	    		do_write(&vesa_mess);
	    		break;
		case DEV_IOCTL:
		    	do_ioctl(&vesa_mess);
		    	break; 
		default:
		    	vesa_reply(TASK_REPLY, vesa_mess.m_source, vesa_mess.PROC_NR, EINVAL);
	}
  }
}


/*===========================================================================*
 *				do_read					     *
 *===========================================================================*/
PRIVATE void do_read(m_ptr)
message *m_ptr;			/* pointer to message sent to the task */
{
	int r = OK;
	printf("thunderhorse\n");
	vesa_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, r);
}

/*===========================================================================*
 *				do_write				     *
 *===========================================================================*/
PRIVATE void do_write(m_ptr)
register message *m_ptr;	/* pointer to message sent to the task */
{
	int r;
	unsigned wnd_pos;
	unsigned wnd_off;
	unsigned n;
	unsigned offset = m_ptr->POSITION;
	char * src;
	phys_bytes user_phys;

	int screen_size=resolutions[res].w*resolutions[res].h;

	/*Reject if count is invalid*/
	if (offset >= screen_size*3) {
		r = EINVAL;
	} else
	if (m_ptr->COUNT <= 0 || m_ptr->COUNT >screen_size *3) {
		r = EINVAL;
	} else
	if (numap(m_ptr->PROC_NR, (vir_bytes) m_ptr->ADDRESS, m_ptr->COUNT) == 0) {
		r = EFAULT;
	} else {
		/*Set the source physical address*/
		user_phys = proc_vir2phys(proc_addr(m_ptr->PROC_NR), m_ptr->ADDRESS);

		/*Obtain position and offset*/
		wnd_pos = offset / WINDOW_SIZE;
		wnd_off = offset % WINDOW_SIZE;

		/*Set the window only if it has changed since last time*/
		if (wnd_pos != last_wnd_pos)
			set_window(0, 4*wnd_pos);

		/*Truncate len if neccesary*/
		if (offset + m_ptr->COUNT > screen_size * 3)
			r = screen_size * 3 - offset;
		else
			r = m_ptr->COUNT;

		printf("pos = %d, ", m_ptr->POSITION );
		printf("count = %d, ", m_ptr->COUNT );
		printf("wr = %d\n", r );

		if(wnd_off+r>WINDOW_SIZE) {	/* data overruns window */
			n = WINDOW_SIZE - wnd_off;	/* bytes left */

			phys_copy(user_phys, SCREEN_BASE + wnd_off, n);
			/*mem_vesa_copy(src, wnd_off/2, n/2);*/
			set_window(0, last_wnd_pos + 4);		/* move window */
			phys_copy(user_phys + n, SCREEN_BASE, r - n);
			/*mem_vesa_copy(src+n, 0, (len-n)/2);*/
		} else
		{
			phys_copy(user_phys, SCREEN_BASE + wnd_off, r);
			/*mem_vesa_copy(src, wnd_off/2, len/2);*/
		}
	}
	vesa_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, r);
}



/*===========================================================================*
 *				do_open                                                      *
 *===========================================================================*/
PRIVATE void do_open(m_ptr)
message *m_ptr;			/* pointer to message sent to task */
{
	/*Disable hardware scrolling so our video is not messed up*/
	toggle_scroll();

	vesa_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, OK);
}


/*===========================================================================*
 *				do_close				     *
 *===========================================================================*/
PRIVATE  void do_close(m_ptr)
message  *m_ptr;			/* pointer to message sent to task */
{

	/*Re-enable hardware scrolling*/
	toggle_scroll();

	switch_console();

	vesa_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, OK);
}


/*===========================================================================*
 *				do_ioctl	      				     	 *
 *===========================================================================*/
PRIVATE void do_ioctl(m_ptr)
message *m_ptr;			/* pointer to message sent to the task */
{
	res=m_ptr->TTY_REQUEST;


	/*if invalid res*/
	if (res<0 || res > sizeof(resolutions)/sizeof(resT))
	{
		vesa_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, -1);
		return;
	}
	switch_graph();

	vesa_reply(TASK_REPLY, m_ptr->m_source, m_ptr->PROC_NR, OK);
} 


/*===========================================================================*
 *	                    switch_graph                                       *
 *===========================================================================*/
PRIVATE void switch_graph()
{
	/*Set the registers to enable SuperGA Mode*/
	reg86.b.intno = 0x10;

	/*Choose 'SET SuperVGA VIDEO MODE'*/
	reg86.b.ah = 0x4F;
	reg86.b.al = 0x02;

	reg86.w.bx = resolutions[res].def; 
	
	/*Perform the BIOS interrupt*/

	level0(int86);
}


/*===========================================================================*
 *				switch_console 						 *
 *===========================================================================*/
PRIVATE  void switch_console()
{

	/*Set the registers to enable SuperGA Mode*/
	reg86.b.intno = 0x10;

	/*Choose 'SET SuperVGA VIDEO MODE'*/
	reg86.b.ah = 0x4F;
	reg86.b.al = 0x02;

	/*640x480, clear screen*/
	reg86.w.bx = 0x3;

	/*Perform the BIOS interrupt*/
	level0(int86);
}

/*===========================================================================*
 *				set_window                                                   *
 *===========================================================================*/
PRIVATE void set_window(window,position)
unsigned window;
unsigned position;
{
/* repositions the indicates memory window to the new
 * position (in WinGranularity units). */

	last_wnd_pos = position;

	/*Set the registers to change the window*/
	reg86.b.intno = 0x10;

	/*Choose Display Window Control*/
	reg86.b.ah = 0x4F;
	reg86.b.al = 0x05;
	reg86.b.bh = 0x00;
	reg86.b.bl = window;
	reg86.w.dx = position;

	/*Perform the BIOS interrupt*/
	level0(int86);
}


/*===========================================================================*
 *				vesa_reply                                                   *
 *===========================================================================*/
PRIVATE void vesa_reply(code, replyee, proc_nr, status)
int code;			/* TASK_REPLY or REVIVE */
int replyee;		/* destination address for the reply */
int proc_nr;		/* to whom should the reply go? */
int status;			/* reply code */
{
/* Send a reply to a process that wanted to read or write data. */

	message vesa_mess;

	vesa_mess.m_type = code;
	vesa_mess.REP_PROC_NR = proc_nr;
	vesa_mess.REP_STATUS = status;
	if ((status = send(replyee, &vesa_mess)) != OK)
		panic("vesa_reply failed, status\n", status);
}

